﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media;
using System.Diagnostics;
using System.Windows.Media.Imaging;
using Microsoft.Surface.Presentation.Controls;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Master
{
    /*
     * Lane is used inside a road and required to let a car drive.
     * It can contain a trafficlight and a direction arrow.
     * It contains properties for max speed, allowed directions, orientation, type and if it's a ramp.
     * 
     * @Creator: T.T. Dusseldorp
     * @Changes made by:
     *      - A.J. Janssen
     *      - V.L. de Jager
     */
    public class Lane : Label
    {
        /*************
         * CONSTANTS *
         *************/
        private const int FORWARD = 0, LEFT = 1, RIGHT = 2, FORWARDLEFT = 3, FORWARDRIGHT = 4, LEFTRIGHT = 5, FORWARDLEFTRIGHT = 6, NONE = 7;

        /*************
         * Variables *
         *************/
        public TrafficLight trafficLight = null;
        private Image arrowImage;
        private InterSection interSection;
        private List<Direction> AllowedDirectionsList = new List<Direction>();

        /*************************
         * DEPENDENCY PROPERTIES *
         *************************/
        public static readonly DependencyProperty AllowedDirectionsProperty, MaxSpeedProperty,
            OrientationProperty, TypeProperty, isARampProperty;

        public double MaxSpeed
        {
            get { return (double)this.GetValue(MaxSpeedProperty); }
            set { this.SetValue(MaxSpeedProperty, value); }
        }

        public int Orientation
        {
            get { return (int)this.GetValue(OrientationProperty); }
            set { this.SetValue(OrientationProperty, value); }
        }

        public int isARamp
        {
            get { return (int)this.GetValue(isARampProperty); }
            set { this.SetValue(isARampProperty, value); }
        }
        public LaneType Type
        {
            get { return (LaneType)this.GetValue(TypeProperty); }
            set { this.SetValue(TypeProperty, value); }
        }

        public int AllowedDirections
        { 
            get { return (int)this.GetValue(AllowedDirectionsProperty); }
            set { this.SetValue(AllowedDirectionsProperty, value); }
        }

        private static void AllowedDirectionsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            List<Direction> directions = new List<Direction>();
            switch ((int)e.NewValue)
            {
                case FORWARD:
                    directions.Add(Direction.FORWARD);
                    break;
                case LEFT:
                    directions.Add(Direction.LEFT);
                    break;
                case RIGHT:
                    directions.Add(Direction.RIGHT);
                    break;
                case FORWARDLEFT:
                    directions.Add(Direction.FORWARD);
                    directions.Add(Direction.LEFT);
                    break;
                case FORWARDRIGHT:
                    directions.Add(Direction.FORWARD);
                    directions.Add(Direction.RIGHT);
                    break;
                case LEFTRIGHT:
                    directions.Add(Direction.LEFT);
                    directions.Add(Direction.RIGHT);
                    break;
                case FORWARDLEFTRIGHT:
                    directions.Add(Direction.FORWARD);
                    directions.Add(Direction.LEFT);
                    directions.Add(Direction.RIGHT);
                    break;
                case NONE:
                    directions.Clear();
                    break;
                default:
                    throw new InvalidOperationException("No allowed direction given!");
            }
            Lane l = (Lane)d;
            l.AllowedDirectionsList = directions;
        }

        /******************
         * STATIC METHODS *
         ******************/

        /*
         * Function used to determine if the lane is next to anonother lane.
         * will return true or false depending on the outcome.
         */
        public static Boolean laneIsNextToLane(Lane lane, Lane otherLane)
        {
            Point lanePoint = Toolkit.getCenterPoint(lane);
            switch (lane.Orientation)
            {
                case 0:
                case 180:
                    return (Map.INSTANCE.getLaneAtPoint(new Point(lanePoint.X + lane.ActualWidth, lanePoint.Y)) == otherLane ||
                        Map.INSTANCE.getLaneAtPoint(new Point(lanePoint.X - lane.ActualWidth, lanePoint.Y)) == otherLane);
                case 90:
                case 270:
                    return (Map.INSTANCE.getLaneAtPoint(new Point(lanePoint.X, lanePoint.Y + lane.ActualHeight)) == otherLane ||
                        Map.INSTANCE.getLaneAtPoint(new Point(lanePoint.X, lanePoint.Y - lane.ActualHeight)) == otherLane);
                default: throw new InvalidOperationException("Invalid lane orientation");
            }
        }

        /*
         * Returns a list of every lane that contain the given direction.
         * Need a direction and an array or list of lanes.
         */
        public static List<Lane> getLanesWithAllowedDirection(Lane[] lanes, Direction direction)
        {
            List<Lane> resultList = new List<Lane>();
            foreach (Lane lane in lanes)
                if (lane.getAllowedDirections().Contains(direction))
                    resultList.Add(lane);
            return resultList;
        }

        public static List<Lane> getLanesWithAllowedDirection(List<Lane> lanes, Direction direction)
        {
            List<Lane> resultList = new List<Lane>();
            foreach (Lane lane in lanes)
                if (lane.getAllowedDirections().Contains(direction))
                    resultList.Add(lane);
            return resultList;
        }

        /*
         * Returns a list of every lane that only contains the given direction.
         * Need a direction and an array or list of lanes.
         */
        public static List<Lane> getLanesAllowingOnlyDirection(List<Lane> lanes, Direction direction)
        {
            List<Lane> resultList = new List<Lane>();
            foreach (Lane lane in lanes)
            {
                List<Direction> allowedDirections = lane.getAllowedDirections();
                if (allowedDirections.Count == 1 && allowedDirections.Contains(direction))
                    resultList.Add(lane);
            }
            return resultList;
        }

        public static List<Lane> getLanesAllowingOnlyDirection(Lane[] lanes, Direction direction)
        {
            List<Lane> resultList = new List<Lane>();
            foreach (Lane lane in lanes)
            {
                List<Direction> allowedDirections = lane.getAllowedDirections();
                if (allowedDirections.Count == 1 && allowedDirections.Contains(direction))
                    resultList.Add(lane);
            }
            return resultList;
        }

        /*
         * Returns a list of every lane that doesn't contain the gives direction.
         * Need a direction and an array or list of lanes.
         */
        public static List<Lane> getLanesWithoutDirection(List<Lane> lanes, Direction direction)
        {
            List<Lane> resultList = new List<Lane>();
            foreach (Lane lane in lanes)
                if (!lane.getAllowedDirections().Contains(direction))
                    resultList.Add(lane);
            return resultList;
        }

        public static List<Lane> getLanesWithoutDirection(Lane[] lanes, Direction direction)
        {
            List<Lane> resultList = new List<Lane>();
            foreach (Lane lane in lanes)
                if (!lane.getAllowedDirections().Contains(direction))
                    resultList.Add(lane);
            return resultList;
        }

        /*
         * Returns a list of every lane with the same orientation
         * Need a orientation and an array or list of lanes.
         */
        public static List<Lane> getLanesWithOrientation(Lane[] lanes, int orientation)
        {
            List<Lane> resultList = new List<Lane>();
            foreach (Lane lane in lanes)
                if (lane.Orientation == orientation)
                    resultList.Add(lane);
            return resultList;
        }

        public static List<Lane> getLanesWithOrientation(List<Lane> lanes, int orientation)
        {
            List<Lane> resultList = new List<Lane>();
            foreach (Lane lane in lanes)
                if (lane.Orientation == orientation)
                    resultList.Add(lane);
            return resultList;
        }

        /****************
         * CONSTRUCTORS *
         ****************/

        /*
         * Inside this static constructor all metadata and events will be set for the depency properties. 
         */
        static Lane()
        {
            FrameworkPropertyMetadata AllowedDirectionsPropertyMetaData = new FrameworkPropertyMetadata(-1,
                    new PropertyChangedCallback(Lane.AllowedDirectionsPropertyChanged));

            FrameworkPropertyMetadata MaxspeedPropertyMetaData = new FrameworkPropertyMetadata((double)20);
            FrameworkPropertyMetadata OrientationPropertyMetaData = new FrameworkPropertyMetadata((int)0);
            FrameworkPropertyMetadata isARampMetaData = new FrameworkPropertyMetadata((int)0);
            FrameworkPropertyMetadata TypePropertyMetaData = new FrameworkPropertyMetadata((LaneType)LaneType.DEFAULT);

            AllowedDirectionsProperty = DependencyProperty.Register("AllowedDirections",
                    typeof(int), typeof(Lane), AllowedDirectionsPropertyMetaData);
            MaxSpeedProperty = DependencyProperty.Register("MaxSpeed",
                typeof(double), typeof(Road), MaxspeedPropertyMetaData);
            OrientationProperty = DependencyProperty.Register("Orientation",
                typeof(int), typeof(Lane), OrientationPropertyMetaData);
            TypeProperty = DependencyProperty.Register("Type",
                typeof(LaneType), typeof(Lane), TypePropertyMetaData);
            isARampProperty = DependencyProperty.Register("isARamp",
                typeof(int), typeof(Lane), isARampMetaData);
        }

        /*
         * public constructor used to set the initialization events.
         */
        public Lane()
        {
            this.Type = LaneType.DEFAULT;
            this.Initialized += Lane_Initialized;
            Map.INSTANCE.Initialized += Map_Initialized;
         //   Map.INSTANCE.Initialized 
            Map.INSTANCE.Loaded += Map_Initialized;
        }

        /******************
         * INITIALIZATION *
         ******************/

        private void Lane_Initialized(object sender, EventArgs e)
        {
           if(isARamp == 1) drawRamp();
           else
               switch (Orientation)
               {
                   case 0:
                       Background = Brushes.Gray;
                       break;
                   case 90:
                       Background = Brushes.Gray;
                       break;
                   case 180:
                       this.Background = Brushes.Gray;
                       break;
                   case 270:
                       this.Background = Brushes.Gray;
                       break;
               };
            if (AllowedDirections == -1)
            {
                throw new System.ArgumentException("Property must be set", "AllowedDirections");
            }
            if (AllowedDirections == 7)
            {
                BitmapImage imageAfrit = new BitmapImage();
                imageAfrit.BeginInit();
                imageAfrit.UriSource = new Uri(BaseUriHelper.GetBaseUri(this), "Images/afrit.png");
              
              switch (Orientation)
                {
                    case 0:
                        break;
                    case 90:
                        imageAfrit.Rotation = Rotation.Rotate90;
                        break;
                    case 180:
                        imageAfrit.Rotation = Rotation.Rotate180;
                        break;
                    case 270: imageAfrit.Rotation = Rotation.Rotate270;
                        break;
                }
                
                imageAfrit.EndInit();
                this.Background = new ImageBrush(imageAfrit);
            }
        }

		void Map_Initialized(object sender, EventArgs e)
        {
            setLaneBorders();
        }
        /********************
         * PUBLIC FUNCTIONS *
         ********************/

        /*
         * function used to set the borders to the lane.
         */
        public void setLaneBorders()
        { 
            Line rectan = new Line();
            rectan.Stroke = Brushes.White;
            rectan.StrokeDashCap = PenLineCap.Square;
            rectan.StrokeThickness = 3;
            DoubleCollection doub = new DoubleCollection();
            doub.Add(7);
            Point point = new Point();
             point = Toolkit.getPointRelativeToMap(this);
             if (this.hasTrafficLight())
             {
                 rectan.StrokeThickness = 10;
                 doub.RemoveAt(0);
                 doub.Add(0.3); 
                 doub.Add(3); 

             } 
            rectan.StrokeDashArray = doub;

             switch (Orientation)
             {
                 case 0:
                      rectan.X1 = point.X;
                      rectan.X2 = point.X ;
                      rectan.Y1 = point.Y;
                      rectan.Y2 = point.Y + this.ActualHeight;
                      //point = checkLaneOrient(rectan, new Point(point.X - (this.ActualWidth / 2), point.Y));
                     break;
                 case 90:
                         rectan.X1 = point.X;
                         rectan.X2 = point.X + this.ActualWidth;
                      rectan.Y1 = point.Y;
                      rectan.Y2 = point.Y ;
                      //point = checkLaneOrient(rectan, new Point(point.X + 10, point.Y - (this.ActualHeight / 2)));
                     break;
                 case 180:
                     rectan.X1 = point.X + this.ActualWidth;
                     rectan.X2 = point.X + this.ActualWidth;
                      rectan.Y1 = point.Y;
                      rectan.Y2 = point.Y + this.ActualHeight;
                      //point = checkLaneOrient(rectan, new Point(point.X + (this.ActualWidth * 1.5), point.Y));
                     break;
                 case 270:
                    rectan.X1 = point.X;
                    rectan.X2 = point.X + this.ActualWidth;
                    rectan.Y1 = point.Y + this.ActualHeight;
                    rectan.Y2 = point.Y + this.ActualHeight;

                    //point = checkLaneOrient(rectan, new Point(point.X, point.Y + (this.ActualHeight * 1.5)));
                     break;
             }
           
           
       Canvas.SetZIndex(rectan, 2);
       
      Map.INSTANCE.Children.Add(rectan);
        }

        private Point checkLaneOrient(Line rectan, Point point)
        {
            if (Map.INSTANCE.getLaneAtPoint(point) != null)
            {
                if (Map.INSTANCE.getLaneAtPoint(point).Orientation != this.Orientation)
                {
                    setStroke(rectan);
                }
            }
            return point;
        }

        /*
         * Function used to set a trafficlight to a lane.
         * This function is usually called by the intersection.
         */
        public void setTrafficLight()
        {
            if (trafficLight != null)
                return;
            TrafficLight t = new TrafficLight();
            t.Height = 50;
            t.Width = 50;
            t.Orientation = Orientation;
            Point trafficLightLocation = getTrafficLightLocation(t);
            trafficLight = t;
            Canvas.SetTop(t, trafficLightLocation.Y);
            Canvas.SetLeft(t, trafficLightLocation.X);
            Canvas.SetZIndex(t, 3);
            Map.INSTANCE.Children.Add(t);
        }

        /*
         * Determines if the lane has a trafficlight.
         */ 
        public Boolean hasTrafficLight()
        {
            return (trafficLight != null);
        }

        /*
         * Function used to get the startpoint from a lane
         */
        public Point getStartPoint()
        {
            Point point = new Point();
            Rect bounds = Toolkit.getBounds(this);
            if (this.Orientation == 0)
            {
                point.X = (bounds.Left + bounds.Right) / 2;
                point.Y = bounds.Bottom;
            }
            else if (this.Orientation == 90)
            {
                point.X = bounds.Left;
                point.Y = (bounds.Top + bounds.Bottom) / 2;
            }
            else if (this.Orientation == 180)
            {
                point.X = (bounds.Left + bounds.Right) / 2;
                point.Y = bounds.Top;
            }
            else if (this.Orientation == 270)
            {
                point.X = bounds.Right;
                point.Y = (bounds.Top + bounds.Bottom) / 2;
            }
            return point;
        }

        /*
         * Function used to get the destination point from a lane
         */
        public Point getDestinationPoint()
        {
            Point point = new Point();
            Rect bounds = Toolkit.getBounds(this);
            if (this.Orientation == 0)
            {
                point.X = (bounds.Left + bounds.Right) / 2;
                point.Y = bounds.Top;
            }
            else if (this.Orientation == 90)
            {
                point.X = bounds.Right;
                point.Y = (bounds.Top + bounds.Bottom) / 2;
            }
            else if (this.Orientation == 180)
            {
                point.X = (bounds.Left + bounds.Right) / 2;
                point.Y = bounds.Bottom;
            }
            else if (this.Orientation == 270)
            {
                point.X = bounds.Left;
                point.Y = (bounds.Top + bounds.Bottom) / 2;
            }
            return point;
        }

        /*
         * Function used to set a traffic arrow at a lane.
         */
        public void setTrafficArrow()
        {
            if (Type != LaneType.DRIVEWAY) return;
            arrowImage = createArrowImage();
            Point p = getTrafficArrowLocation(arrowImage);
            Canvas.SetTop(arrowImage, p.Y);
            Canvas.SetLeft(arrowImage, p.X);
            Canvas.SetZIndex(arrowImage, 1);
            Map.INSTANCE.Children.Add(arrowImage);
            setMenuArrows(arrowImage);
        }

        /*
         * Function used to set arrow menu to a lane.
         * Used to set lane directions on the go.
         */
        public void setMenuArrows(Image img)
        {
            Point point = getTrafficArrowLocation(img);
            ElementMenu menu = new ElementMenu();
            menu.ActivationMode = ElementMenuActivationMode.HostInteraction;
            menu.ActivationHost = img;
            menu.VerticalAlignment = System.Windows.VerticalAlignment.Center;
            menu.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
            menu.Orientation = 90;
            addMenuItems(menu);
            Canvas.SetTop(menu, point.Y);
            Canvas.SetLeft(menu, point.X);
            Canvas.SetZIndex(menu, 11);
            Map.INSTANCE.Children.Add(menu);
        }

        /*
         * Function used to set the lane direction
         */
        public void setLaneDirection(object sender, RoutedEventArgs e)
        {
            ElementMenuItem menuitem = (ElementMenuItem)sender;
            AllowedDirections = (int) menuitem.CommandParameter;
            Map.INSTANCE.Children.Remove(arrowImage);
            Map.INSTANCE.Children.Remove((ElementMenu)menuitem.Parent);
            setTrafficArrow();
            notifyCars();
        }

        public void setInterSection(InterSection interSection)
        {
            this.interSection = interSection;
        }

        public InterSection getInterSection()
        {
            return interSection;
        }

        public void setAllowedDirections(List<Direction> AllowedDirections)
        {
            this.AllowedDirectionsList = AllowedDirections;
        }

        public List<Direction> getAllowedDirections()
        {
            return AllowedDirectionsList;
        }

        public void setTurnLane()
        {
            if (trafficLight != null)
                removeTrafficLight();
            if (arrowImage != null)
                removeArrow();
        }

        private void removeTrafficLight()
        {
            Map.INSTANCE.Children.Remove(trafficLight);
            trafficLight = null;
        }

        private void removeArrow()
        {
            Map.INSTANCE.Children.Remove(arrowImage);
        }

        /*********************
         * PRIVATE FUNCTIONS *
         *********************/
        private void drawRamp()
        {
            BitmapImage imageOprit = new BitmapImage();
            imageOprit.BeginInit();
            imageOprit.UriSource = new Uri(BaseUriHelper.GetBaseUri(this), "Images/oprit.png");
            switch (Orientation)
            {
                case 0:
                    break;
                case 90:
                    imageOprit.Rotation = Rotation.Rotate90;
                    break;
                case 180:
                    imageOprit.Rotation = Rotation.Rotate180;
                    break;
                case 270: imageOprit.Rotation = Rotation.Rotate270;
                    break;
            }

            imageOprit.EndInit();
            this.Background = new ImageBrush(imageOprit);
        }

        private Image createArrowImage()
        {
            Image arrowImage = new Image();
            arrowImage.Height = 50;
            arrowImage.Width = 50;
            arrowImage.RenderTransform = new RotateTransform(Orientation);
            switch (AllowedDirections)
            {
                case FORWARD:
                    arrowImage.Source = new BitmapImage(new Uri(@"/Master;component/Images/arrowUp.png", UriKind.Relative));
                    break;
                case LEFT:
                    arrowImage.Source = new BitmapImage(new Uri(@"/Master;component/Images/arrowLeft.png", UriKind.Relative));
                    break;
                case RIGHT:
                    arrowImage.Source = new BitmapImage(new Uri(@"/Master;component/Images/arrowRight.png", UriKind.Relative));
                    break;
                case FORWARDLEFT:
                    arrowImage.Source = new BitmapImage(new Uri(@"/Master;component/Images/arrowLeftForward.png", UriKind.Relative));
                    break;
                case FORWARDRIGHT:
                    arrowImage.Source = new BitmapImage(new Uri(@"/Master;component/Images/arrowRightForward.png", UriKind.Relative));
                    break;
                case LEFTRIGHT:
                    arrowImage.Source = new BitmapImage(new Uri(@"/Master;component/Images/arrowLeftRight.png", UriKind.Relative));
                    break;
                case FORWARDLEFTRIGHT:
                    arrowImage.Source = new BitmapImage(new Uri(@"/Master;component/Images/noArrow.png", UriKind.Relative));
                    break;
                case NONE:
                    arrowImage.Source = new BitmapImage(new Uri(@"/Master;component/Images/noArrow.png", UriKind.Relative));
                    break;
            }
            return arrowImage;
        }

        private void addMenuItems(ElementMenu menu)
        {
            menu.Items.Add(createMenuItem("Rechtdoor", FORWARD));
            menu.Items.Add(createMenuItem("Linksaf", LEFT));
            menu.Items.Add(createMenuItem("Rechtsaf", RIGHT));
            menu.Items.Add(createMenuItem("Rechtdoor & Linksaf", FORWARDLEFT));
            menu.Items.Add(createMenuItem("Rechtdoor & Rechtsaf", FORWARDRIGHT));
            menu.Items.Add(createMenuItem("Linksaf & Rechtsaf", LEFTRIGHT));
            menu.Items.Add(createMenuItem("Alle richtingen", FORWARDLEFTRIGHT));
            menu.Items.Add(createMenuItem("Geen richtingen(afrit)", NONE));
        }

        private ElementMenuItem createMenuItem(String header, int commandParameter)
        {
            Image iconss = new Image();
            iconss.Source = new BitmapImage(new Uri(@"/Master;component/Images/icons/" + commandParameter + ".png", UriKind.Relative));
            ElementMenuItem item = new ElementMenuItem();
            item.Click += setLaneDirection;
            item.Header = header;
            item.Icon = iconss;
            item.CommandParameter = commandParameter;
            if (AllowedDirections == commandParameter) item.Background = Brushes.LawnGreen;
            return item;
        }

        private void notifyCars()
        {
            List<Car> cars = Map.INSTANCE.getCarsAtBounds(Toolkit.getBounds(this));
            foreach (Car car in cars) if (Map.INSTANCE.getIntersectionAtBounds(car.getTrueBounds()) == null)
                    car.generateNewNextDirection(AllowedDirectionsList);
        }

        private static void setStroke(Line rectan)
        {
            rectan.StrokeThickness = 3;
            DoubleCollection douba = new DoubleCollection();
            douba.Add(0);
            rectan.StrokeDashArray = douba;
        }

        private Point getTrafficLightLocation(TrafficLight t)
        {
            Rect bounds = Toolkit.getBounds(this);
            Point trafficLightLocation = new Point();
            switch (Orientation)
            {
                case 0:
                    trafficLightLocation.X = bounds.Left + ((bounds.Width - t.Width) / 2);
                    trafficLightLocation.Y = bounds.Top;
                    break;
                case 90:
                    trafficLightLocation.X = bounds.Left + (bounds.Width - t.Width);
                    trafficLightLocation.Y = bounds.Top + ((bounds.Height - t.Height) / 2);
                    break;
                case 180:
                    trafficLightLocation.X = bounds.Left + ((bounds.Width - t.Width) / 2);
                    trafficLightLocation.Y = bounds.Top + (bounds.Height - t.Height);
                    break;
                case 270:
                    trafficLightLocation.X = bounds.Left;
                    trafficLightLocation.Y = bounds.Top + ((bounds.Height - t.Height) / 2);
                    break;
                default: throw new InvalidOperationException("This lane has a unknown orientation");
            }
            return trafficLightLocation;
        }

        private Point getTrafficArrowLocation(Image img)
        {
            Rect bounds = Toolkit.getBounds(this);
            Point trafficArrowLocation = new Point();
            switch (Orientation)
            {
                case 0:
                    trafficArrowLocation.X = bounds.Left + ((bounds.Width - img.Width) / 2);
                    trafficArrowLocation.Y = bounds.Top + 50;
                    break;
                case 90:
                    trafficArrowLocation.X = bounds.Left + (bounds.Width - img.Width);
                    trafficArrowLocation.Y = bounds.Top + ((bounds.Height - img.Height) / 2);
                    break;
                case 180:
                    trafficArrowLocation.X = bounds.Left + ((bounds.Width - img.Width) / 2) + 45;
                    trafficArrowLocation.Y = bounds.Top + (bounds.Height - img.Height);
                    break;
                case 270:
                    trafficArrowLocation.X = bounds.Left + 50;
                    trafficArrowLocation.Y = bounds.Top + ((bounds.Height - img.Height) / 2) + 50;
                    break;
                default: throw new InvalidOperationException("This lane has a unknown orientation");
            }
            return trafficArrowLocation;
        }
    }
}
